package cn.itcast.hotel;

import cn.itcast.hotel.pojo.HotelDoc;
import cn.itcast.hotel.pojo.ItemDoc;
import com.alibaba.fastjson.JSON;
import org.apache.http.HttpHost;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@SpringBootTest
public class HotelRestClient {
    static final String SEARCH_INDICES = "hotel";
    static final String SEARCH_INDICES_ITEMS = "items";

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    /**
     * 使用完成后关闭es连接
     *
     * @throws IOException
     */
    @BeforeEach
    void setUp() {
        this.restHighLevelClient = new RestHighLevelClient(RestClient.builder(
//                HttpHost.create("http://129.28.83.153:9200")
                HttpHost.create("http://113.45.170.115:9200")
        ));
    }

    /**
     * 使用完成后关闭es连接
     *
     * @throws IOException
     */
    @AfterEach
    void tearDown() throws IOException {
        this.restHighLevelClient.close();
    }

    /*
     * 使用restClient操作查询
     */

    @Test
    void testMatchAll() throws IOException {
//        1.准备请求体
        SearchRequest request = new SearchRequest(SEARCH_INDICES);
//        2.组织DSL参数
        request.source().query(QueryBuilders.matchAllQuery());
//        3.发送请求，得到响应结果
        SearchResponse searchResponse = restHighLevelClient.search(request, RequestOptions.DEFAULT);
//        4.解析结果
        handleRespose(searchResponse);
    }

    private void handleRespose(SearchResponse searchResponse) throws IOException {

//        4.解析结果(解析json格式的数据)
        SearchHits searchHits = searchResponse.getHits();
//        4.1获取总条数 方便进行遍历
        long total = searchHits.getTotalHits().value;
        System.out.println("共搜索到" + total + "条数据");
//        4.2查询的结果数组
        SearchHit[] hits = searchHits.getHits();
//        4.3遍历所有的hits元素
//        4.3遍历所有的hits元素
        for (SearchHit hit : hits) {
//            4.4获得source
            String sourceAsString = hit.getSourceAsString();
//            4.6反序列化 适合返回给前端DOC数据
            HotelDoc hotelDoc = JSON.parseObject(sourceAsString, HotelDoc.class);
//            System.out.println("hotelDoc = " + hotelDoc);
            //            4.5获取高亮部分
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
//            通用的结果 判断是否包含高亮字段
            if (!CollectionUtils.isEmpty(highlightFields)) {
//                简化了 默认只有一个高亮的部分 其实有多个需要拼接
                HighlightField name = highlightFields.get("name");
//                获取数组中的第一个
                String stringName = name.getFragments()[0].toString();
//                替换掉原型文档doc的高亮
                hotelDoc.setName(stringName);
            }
//            打印文旦查看高亮信息
            System.out.println("doc = " + hotelDoc);
        }
    }

    @Test
    void testMatch() throws IOException {
//        1.构建查询
        SearchRequest searchRequest = new SearchRequest(SEARCH_INDICES);
//        2.构建DSL语句
        searchRequest.source().query(QueryBuilders.matchQuery("all", "如家"));
//        3.发送请求
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//        3.解析结果
        handleRespose(searchResponse);
    }

    /**
     * 多字段查询
     *
     * @throws IOException
     */
    @Test
    void testMultiMatch() throws IOException {
        SearchRequest searchRequest = new SearchRequest(SEARCH_INDICES);
        searchRequest.source().query(QueryBuilders.multiMatchQuery("如家", "name", "brand"));
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        handleRespose(searchResponse);
    }

    /**
     * 精确查询 term 词条查询 和 range 范围查询
     */
    @Test
    void testTermAndRange() throws IOException {
        SearchRequest searchRequest = new SearchRequest(SEARCH_INDICES);
        searchRequest.source()
//                1.term 词条查询
//                .query(QueryBuilders.termQuery("city", "北京"));
//        2. 范围查询 range
                .query(QueryBuilders.rangeQuery("price").gte(100).lte(150));
        SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        handleRespose(response);
    }

    /**
     * 布尔查询
     */
    @Test
    void testBool() throws IOException {
        SearchRequest searchRequest = new SearchRequest(SEARCH_INDICES);
//        准备DSL语句
      /*  searchRequest.source()
//               构造boolQuery对象
                .query(QueryBuilders.boolQuery()
//                        在must中添加 term词条查询
                        .must(QueryBuilders.termQuery("city", "北京"))
//                        在filter 过滤条件中 添加 range范围查询
                        .filter(QueryBuilders.rangeQuery("price").lte(250)));*/

        // 2.准备DSL
        // 2.1.准备BooleanQuery
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        // 2.2.添加term
        boolQuery.must(QueryBuilders.termQuery("city", "北京"));
        // 2.3.添加range
        boolQuery.filter(QueryBuilders.rangeQuery("price").lte(250));

        searchRequest.source().query(boolQuery);

//        发送请求
        SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//        解析响应
        handleRespose(response);
    }

    /**
     * 案例 搜索关键字为牛奶
     * 品牌必须为安佳
     * 价格必须低于300
     */
    @Test
    void testHomework01() throws IOException {
//    1.构建查询请求
        SearchRequest searchRequest = new SearchRequest(SEARCH_INDICES_ITEMS);
//        2.构建DSL语句（多条件的组合）
        searchRequest.source()
                .query(QueryBuilders.boolQuery()
                        .must(QueryBuilders.matchQuery("name", "牛奶"))
                        .filter(QueryBuilders.termQuery("brand", "安佳"))
                        .filter(QueryBuilders.rangeQuery("price").lt(30000)));
//        3.发送请求
        SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//        4.解析响应结果
        long value = response.getHits().getTotalHits().value;
        System.out.println("总数===" + value);
        SearchHit[] hits = response.getHits().getHits();
        for (SearchHit hit : hits) {
            String json = hit.getSourceAsString();
            ItemDoc itemDoc = JSON.parseObject(json, ItemDoc.class);
            System.out.println(itemDoc);
        }
    }

    /**
     * 分页和排序
     */
    @Test
    void testOrderAndLimit() throws IOException {
        SearchRequest searchRequest = new SearchRequest(SEARCH_INDICES_ITEMS);
        searchRequest.source()
//                分页和排序需要对所有的查询 所以是matchAll
                .query(QueryBuilders.matchAllQuery())
//                分页 起始页
                .from(0)
//                分页的 页数大小
                .size(7)
//                排序 排序字段和 排序方式(可以多个字段的排序 平级继续按照下一个字段排序)
                .sort("sold", SortOrder.ASC)
                .sort("price", SortOrder.DESC);
        SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        handleRespose(response);
    }

    /**
     * 高亮的api
     */
    @Test
    void testHighLight() throws IOException {
//        1.准备请求对象
        SearchRequest searchRequest = new SearchRequest(SEARCH_INDICES);
//        2.准备DSL语句（高亮查询必须使用全文检索查询，并且要有搜索关键字，将来才可以对关键字高亮。）
        searchRequest.source().query(QueryBuilders.matchQuery("all", "如家"))
//                高亮处理
                .highlighter(new HighlightBuilder()
                        .field("name")
                        .requireFieldMatch(false));
//        3.发送请求
        SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//        4.解析结果
        handleRespose(response);
    }

    /**
     * aggs的聚合操作
     */
    @Test
    void testAggs() throws IOException {
//       1.准备请求对象
        SearchRequest searchRequest = new SearchRequest(SEARCH_INDICES_ITEMS);
//       2.DSL的语句聚合
        String brandAgg = "brand_agg";
        searchRequest.source().size(0).aggregation(
                AggregationBuilders
                        .terms(brandAgg)
                        .field("brand")
                        .size(10)
        );
//        3。发送请求
        SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//        4.解析响应
        long value = response.getHits().getTotalHits().value;
        System.out.println("总共有 " + value + "条数据");
        Aggregations aggregations = response.getAggregations();
//        使用的是什么聚合 解析的时候就需要使用对应的解析方式进行解析
        Terms aggs = aggregations.get(brandAgg);
        List<? extends Terms.Bucket> buckets = aggs.getBuckets();
        for (Terms.Bucket bucket : buckets) {
            String keyAsString = bucket.getKeyAsString();
            System.out.println("数量：" + bucket.getDocCount());
            System.out.println(keyAsString);
        }
    }

    /**
     * 使用聚合功能，利用Bucket聚合，对搜索结果中的文档基于品牌分组、基于城市分组，就能得知包含哪些品牌、哪些城市了。
     */
    @Test
    void testAggsByParam() throws IOException {
//        1.创建请求
        SearchRequest searchRequest = new SearchRequest(SEARCH_INDICES);
//        2.构建聚合函数
        buildResult(searchRequest);
//        3.发送请求
        SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//        4.解析结果
        Map<String, List<String>> map = new HashMap<>();
        List<String> brand = handleMap(response, "brand_agg");
        map.put("品牌", brand);
        List<String> starName = handleMap(response, "starName_agg");
        map.put("星级", starName);
        List<String> city = handleMap(response, "city_agg");
        map.put("城市", city);
        System.out.println(map);
    }

    private static List<String> handleMap(SearchResponse response, String aggName) {
        Aggregations aggregations = response.getAggregations();
        Terms aggregation = aggregations.get(aggName);
        List<? extends Terms.Bucket> buckets = aggregation.getBuckets();
        List<String> list = new ArrayList<>();
        for (Terms.Bucket bucket : buckets) {
            String keyAsString = bucket.getKeyAsString();
            list.add(keyAsString);
        }
        return list;
    }

    private void buildResult(SearchRequest searchRequest) {
//        1.brand
        searchRequest.source().size(0).aggregation(
                AggregationBuilders.terms("brand_agg").field("brand").size(100)
        );
//        2.price
        searchRequest.source().size(0).aggregation(
                AggregationBuilders.terms("starName_agg").field("starName").size(100)
        );
//        3.city
        searchRequest.source().size(0).aggregation(
                AggregationBuilders.terms("city_agg").field("city").size(100)
        );
    }

    /**
     * 自动补全的使用测试
     */
    @Test
    void testSuggestion() throws IOException {
//        1.准备request请求
        SearchRequest searchRequest = new SearchRequest(SEARCH_INDICES);
//        2.DSL语句的 suggestion组装
        String SuggestName = "mySugg";
        searchRequest.source().
                suggest(new SuggestBuilder().addSuggestion(
//                        自动补全结果的名称
                        SuggestName,
                        SuggestBuilders
//                                自动补全字段
                                .completionSuggestion("suggestion")
//                                根据那个拼音自动补全 就是前缀
                                .prefix("ht")
//                                是否跳过重复的
                                .skipDuplicates(true)
                                .size(10)
                ));
//        3.发请求
        SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//        4.解析结果
//        4.1 获取suggest
        Suggest suggest = response.getSuggest();
//        4.2 获取自动补全的结果
//        Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> suggestion = suggest.getSuggestion(SuggestName);
//        可以化简
        CompletionSuggestion suggestion = suggest.getSuggestion(SuggestName);
//        4.3 获取内容
        List<CompletionSuggestion.Entry.Option> options = suggestion.getOptions();
//        4.4 遍历每一个内容 获取内容
        for (CompletionSuggestion.Entry.Option option : options) {
            String string = option.getText().toString();
            System.out.println(string);
        }
    }
}
